package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.article.mapper.ApArticleConfigMapper;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.ApArticleService;
import com.heima.common.constants.ArticleConstants;
import com.heima.common.redis.CacheService;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.pojos.ApArticleConfig;
import com.heima.model.article.pojos.ApArticleContent;
import com.heima.model.article.vos.HotArticleVo;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.mess.ArticleVisitStreamMess;
import com.heima.model.search.vos.SearchArticleVo;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class ApArticleServiceImpl implements ApArticleService {

    @Autowired
    private ApArticleMapper apArticleMapper;

    @Autowired
    private ApArticleConfigMapper configMapper;

    @Autowired
    private ApArticleContentMapper contentMapper;

    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    @Autowired
    private CacheService cacheService;
    /**
     * @param dto  查询的条件
     * @param type 查询的类型 1：查询更多 2 查询更新
     * @return
     */
    @Override
    public ResponseResult loadArticleList(ArticleHomeDto dto, byte type) {
        List<ApArticle> list = apArticleMapper.loadArticleList(dto, type);
        return ResponseResult.okResult(list);
    }

    /**
     * @param dto      查询的条件
     * @param type     查询的类型 1：查询更多 2 查询更新
     * @param fistPage 是否是第一页
     * @return
     */
    @Override
    public ResponseResult loadArticleList2(ArticleHomeDto dto, byte type, boolean fistPage) {
        if(fistPage){
            String json = cacheService.get(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + dto.getTag());// 1
            if(StringUtils.isNotBlank(json)){
                List<HotArticleVo> voList = JSON.parseArray(json, HotArticleVo.class);
                return ResponseResult.okResult(voList);
            }
        }

        return loadArticleList(dto, type);
    }

    /**
     * 保存或者更新文章数据
     *
     * @param dto
     * @return
     */
    @Override
    public ResponseResult saveOrUpdate(ArticleDto dto) {
        //1. 检查参数是否为空
        if(null == dto){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        //2. 拷贝数据到pojo
        ApArticle apArticle = new ApArticle();
        BeanUtils.copyProperties(dto, apArticle);

        //3. 判断传递过来的数据中是否包含了id 如果包含则更新 如果没有则新增
        if(null == apArticle.getId())
        {
            //3.1 保存文章数据
            apArticleMapper.insert(apArticle);

            //保存文章配置
            ApArticleConfig config = new ApArticleConfig(apArticle.getId());
            configMapper.insert(config);

            //保存文章的内容
            ApArticleContent content = new ApArticleContent();
            content.setArticleId(apArticle.getId());
            content.setContent(dto.getContent());
            contentMapper.insert(content);

        }else{
            //3.2 更新文章数据
            apArticleMapper.updateById(apArticle);

            //修改文章的内容
            ApArticleContent articleContent = contentMapper.selectOne(Wrappers.
                    <ApArticleContent>lambdaQuery()
                    .eq(ApArticleContent::getArticleId,
                            apArticle.getId()));
            articleContent.setContent(dto.getContent());

            contentMapper.updateById(articleContent);
        }

        //生成静态的页面

        //同步文章完成以后发送消息到Kafka
        addDocumentToEs(apArticle, dto.getContent());

        //4. 封装返回结果
        return ResponseResult.okResult(apArticle.getId());
    }

    private void addDocumentToEs(ApArticle apArticle, String content) {

        SearchArticleVo vo = new SearchArticleVo();
        BeanUtils.copyProperties(apArticle, vo);
        vo.setContent(content);

        kafkaTemplate.send(ArticleConstants.ARTICLE_ES_SYNC_TOPIC, JSON.toJSONString(vo));
    }

    /**
     * 更新文章的分值  同时更新缓存中的热点文章数据
     *
     * @param mess
     */
    @Override
    public void updateScore(ArticleVisitStreamMess mess) {
        //1. 更新数据库里面的数据
        ApArticle apArticle = updateArticle(mess);
        //2. 重新计算得分
        Integer score = computeScore(apArticle);
        score *= 3;
        //3. 更新文章对应频道的缓存数据
        replaceDataToRedis(apArticle, score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + apArticle.getChannelId());

        //4. 更新推荐的缓存数据
        replaceDataToRedis(apArticle, score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG);
    }

    /**
     * 更新redis中的缓存数据
     * @param apArticle
     * @param score
     * @param key
     */
    private void replaceDataToRedis(ApArticle apArticle, Integer score, String key) {
        String json = cacheService.get(key);
        if(StringUtils.isNotBlank(json)){
            List<HotArticleVo> voList = JSON.parseArray(json, HotArticleVo.class);

            //1. 当前需要更新的文章本来就在缓冲-更新文章的得分即可
            Boolean flag = Boolean.TRUE;

            for (HotArticleVo vo : voList) {
                if(vo.getId().equals(apArticle.getId())){
                    vo.setScore(score);
                    flag = Boolean.FALSE;
                    break;
                }
            }

            if(flag){
                //2. 当前更新的文章不在缓存中
                    //2.1 如果缓存没有达到30表-直接加入缓存即可
                HotArticleVo vo = new HotArticleVo();
                BeanUtils.copyProperties(apArticle, vo);
                vo.setScore(score);

                if(voList.size() < 30){
                    voList.add(vo);
                }else {
                    //2.2 如果缓存里面的数据已经有了30，替换得分最低的一条记录
                    voList = voList.stream()
                            .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                            .collect(Collectors.toList());
                    HotArticleVo lastVo = voList.get(voList.size() - 1);
                    if(lastVo.getScore() < score){
                        voList.remove(lastVo);

                        voList.add(vo);
                    }
                }
            }

            //把数据重新设置到redis中
            voList = voList.stream()
                    .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                    .collect(Collectors.toList());
            cacheService.set(key, JSON.toJSONString(voList));

        }

    }

    private ApArticle updateArticle(ArticleVisitStreamMess mess) {
        ApArticle apArticle = apArticleMapper.selectById(mess.getArticleId());
        if(null != apArticle){
            apArticle.setCollection(apArticle.getCollection() == null?
                    mess.getCollect()
                    :(apArticle.getCollection() + mess.getCollect()));
            apArticle.setComment((apArticle.getComment()==null?mess.getComment():apArticle.getComment())+mess.getComment());
            apArticle.setLikes((apArticle.getLikes()==null?mess.getLike():apArticle.getLikes())+mess.getLike());
            apArticle.setViews((apArticle.getViews()==null?mess.getView():apArticle.getViews())+mess.getView());
        }

        apArticleMapper.updateById(apArticle);

        return apArticle;
    }

    private Integer computeScore(ApArticle apArticle) {
        int score = 0;

        if(apArticle.getLikes() != null){
            score += apArticle.getLikes()* ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT;
        }

        if(apArticle.getViews() != null){
            score += apArticle.getViews();
        }

        if(apArticle.getComment() != null){
            score += apArticle.getComment()* ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT;
        }

        if(apArticle.getCollection() != null){
            score += apArticle.getCollection()* ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT;
        }

        return score;
    }
}
